package com.gitee.kamismile.stoneComEx.common.filter.server;

import com.gitee.kamismile.stone.commmon.util.JsonUtil;
import com.gitee.kamismile.stone.commmon.util.ValueUtils;
import com.gitee.kamismile.stoneComEx.common.Constant;
import com.gitee.kamismile.stoneComEx.common.component.base.LoggerInfoVO;
import com.gitee.kamismile.stoneComEx.util.IpUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebExchangeDecorator;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.*;

/**
 * 对象 form-data form 可以直接获取  json的requestBody 获取
 *     ResponseBody
 *     RequestMapping("/change")
 *      public String change(
 *            A  a
 *     )
 */
public class LoggingReactiveFilter implements ReactiveFilter {

    protected final Logger logger =LoggerFactory.getLogger("bLog");


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        MediaType contentType = request.getHeaders().getContentType();
        if (contentType != null && MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
            // 不处理 multipart
            return chain.filter(exchange);
        }
        return DataBufferUtils.join(request.getBody())
                .defaultIfEmpty(exchange.getResponse().bufferFactory().wrap(new byte[0]))
                .flatMap(dataBuffer -> {
                    byte[] bodyBytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bodyBytes);
                    DataBufferUtils.release(dataBuffer);

                    LogInfoHolder holder = new LogInfoHolder(exchange, bodyBytes);
                    logger.info(MessageFormat.format("请求接口:{0} {1} {2} 调用参数:{3}  IP:{4}", holder.method,
                            holder.path, holder.headers, holder.getBody(), IpUtil.getIpByServerRequest(holder.request)));

                    ServerHttpRequestDecorator decoratedRequest = new ServerHttpRequestDecorator(request) {
                        @Override
                        public Flux<DataBuffer> getBody() {
                            return Flux.just(exchange.getResponse().bufferFactory().wrap(bodyBytes));
                        }
                    };
                    ServerWebExchange decoratedExchange = exchange.mutate().request(decoratedRequest).build();

                    decoratedExchange.getResponse().beforeCommit(() -> {
                        doLog(holder);
                        return Mono.empty();
                    });

                    return chain.filter(decoratedExchange);
                });
    }

//    @Override
//    public Mono<ServerResponse> filter(ServerRequest request, HandlerFunction<ServerResponse> next) {
//        LogInfoHolder holder = new LogInfoHolder(request);
//        return next.handle(request)
//                .doOnSuccessOrError((serverResponse, throwable) -> this.doLog(serverResponse, throwable, holder));
//    }

    private void doLog(LogInfoHolder holder) {
//        Optional.ofNullable(throwable).ifPresent(exception -> logger.error("{}", exception));
        String path = holder.path;
        long start = holder.start;
        long time = System.currentTimeMillis() - start;
        String method = holder.method;
        List<MediaType> types = holder.mediaTypeList;
        Map<String, List<String>> body = holder.getBody();
        if (time > Constant.DEFAULT_TIME_LOG || !CollectionUtils.isEmpty(body.get("requestBody"))) {
//            LoggerInfoVO loggerInfoVO = new LoggerInfoVO();
//            loggerInfoVO.setMethodName(holder.method);
//            loggerInfoVO.setHeaders(JsonUtil.toJson(holder.headers));
//            loggerInfoVO.setUrl(ValueUtils.isStringNull(holder.path));
//            loggerInfoVO.setIp(IpUtil.getIpByServerRequest(holder.request));
//            loggerInfoVO.setRequestBody(JsonUtil.toJson(holder.getBody()));
//            loggerInfoVO.setTime(time);
//            logger.error("{}",JsonUtil.toJson(loggerInfoVO));

            logger.info(MessageFormat.format("调用接口过程:{0} {1} {2} 调用参数:{3} 花费时间:{4}  IP:{5}", method,
                    path, holder.headers, body, time, IpUtil.getIpByServerRequest(holder.request)));
        }
    }

    private class LogInfoHolder {

        private final Map<String, String> headers;
        private String path;
        private long start;
        private String method;
        private List<MediaType> mediaTypeList;
        private Map body;
        private ServerHttpRequest request;
        private byte[] bodyBytes;

        public byte[] getBodyBytes() {
            return bodyBytes;
        }

        public void setBodyBytes(byte[] bodyBytes) {
            this.bodyBytes = bodyBytes;
        }

        /**
         * 不从formData 里面获取数据
         * @param exchange
         * @param bodyBytes
         */
        private LogInfoHolder(ServerWebExchange exchange, byte[] bodyBytes) {
            this.request = exchange.getRequest();
            this.path = request.getPath().value();
            this.start = System.currentTimeMillis();
            this.method = request.getMethod().name();
            this.mediaTypeList = request.getHeaders().getAccept();
            this.headers = this.request.getHeaders().toSingleValueMap();
            this.bodyBytes = bodyBytes;
        }

        public Map<String, List<String>> getBody() {
            MultiValueMap<String, String> params = request.getQueryParams();
            Map<String, List<String>> info = new HashMap<>();
            info.putAll(params);
            if (bodyBytes != null && bodyBytes.length > 0) {
                info.put("requestBody", Arrays.asList(new String(bodyBytes, StandardCharsets.UTF_8)));
            }
            return info;
        }
    }

}
